﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.IO;
using QTPlugin;
using QTPlugin.Interop;
using Kawanon.QtHoney;
using Microsoft.Win32;
using System.Windows.Forms;
using _IDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
using System.Runtime.InteropServices;
using System.Text;

namespace Kawanon.Usability
{
	public class GestureDescription
	{
		public string Category { get; private set; }
		public bool IsAcceptTarget { get; private set; }

		public GestureDescription(string category, bool isAcceptTarget)
		{
			Category = category;
			IsAcceptTarget = isAcceptTarget;
		}
	}

	public class ArchiveVersionMismatchException : ApplicationException
	{
	}

	public sealed class GestureEventArgs : EventArgs
	{
		public GestureExpression Expression { get; private set; }
		public IList<GestureExpression> Suggestions { get; private set; }
		public object Target { get; set; }

		public GestureEventArgs(GestureExpression expr)
		{
			Expression = expr;
		}

		public GestureEventArgs(GestureExpression expr, IList<GestureExpression> suggestions)
		{
			Expression = expr;
			Suggestions = suggestions;
		}
	}

	public delegate void GestureEventHandler(object sender, GestureEventArgs e);

	[Serializable]
	public abstract class Action
	{
		public class Arguments
		{
			public IPluginServer Server { get; set; }
			public IPluginClient Plugin { get; set; }
			public IShellView ShellView { get; set; }
			public object Target { get; set; }
		}

		protected Address[] GetAddresses(object target)
		{
			_IDataObject data = target as _IDataObject;
			if (data != null)
			{
				return Utility.GetAddresses(data);
			}
			Address[] addrs = target as Address[];
			if (addrs != null)
			{
				return addrs;
			}
			return null;
		}

		protected string[] GetFileNames(object target)
		{
			_IDataObject data = target as _IDataObject;
			if (data != null)
			{
				return Utility.GetFileNames(data);
			}
			string[] paths = target as string[];
			if (paths != null)
			{
				return paths;
			}
			return null;
		}

		public abstract string Name { get; }

		public abstract void Execute(Arguments args);

		//protected Action(string name)
		//{
		//    Name = name;
		//}

		public override bool Equals(object obj)
		{
			if (!(obj is Action))
			{
				return false;
			}
			//NOTE: 各クラスで GetHashCode にタイプのハッシュを含めるので不要
			//if (this.GetType() != obj.GetType())
			//{
			//    return false;
			//}
			if (GetHashCode() != obj.GetHashCode())
			{
				return false;
			}

			return true;
		}

		public override int GetHashCode()
		{
			//デフォルトでは同じ型は同じハッシュで
			return ~ this.GetType().GetHashCode();
		}

		//protected int InternalGetHashCode()
		//{
		//    return base.GetHashCode();
		//}

		public static Action From(Commands command, object arg)
		{
			return new BuiltInCommand(command, arg);
		}

		public static Action From(Commands command)
		{
			return new BuiltInCommand(command, null);
		}
	}

	[Serializable]
	public abstract class CustomAction : Action
	{
		private static readonly Random rand = new Random();

		public int Id { get; private set; }

		public virtual bool IsAcceptTarget { get { return false; } }

		public abstract DialogResult ShowEditDialog(IntPtr hwnd);

		public CustomAction()
		{
			Id = rand.Next(Int32.MinValue, Int32.MaxValue);
		}

		//public override int GetHashCode()
		//{
		//    throw new NotSupportedException();
		//}

		public override int GetHashCode()
		{
			//NOTE: ShowEditDialog によって内容が変化するため、同一性をIDとして保持
			return Id;
		}
	}

	[Serializable]
	internal class BuiltInCommand : Action
	{
		[DllImport("shell32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		public static extern bool SHGetPathFromIDListW(IntPtr pidl, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder pszPath);

		private delegate object TargetTranslator(BuiltInCommand cmd, Action.Arguments args);

		private static Dictionary<Commands, string> s_names = new Dictionary<Commands, string>();
		private static Dictionary<Commands, TargetTranslator> s_translators = new Dictionary<Commands, TargetTranslator>();

		static BuiltInCommand()
		{
			// 0.0.1
			s_names.Add(Commands.GoBack, "戻る");
			s_names.Add(Commands.GoForward, "進む");
			s_names.Add(Commands.GoUpOneLevel, "上へ");
			s_names.Add(Commands.RefreshBrowser, "更新");
			s_names.Add(Commands.CloseCurrentTab, "現在のタブを閉じる");
			s_names.Add(Commands.CloseLeft, "左を閉じる");
			s_names.Add(Commands.CloseRight, "右を閉じる");
			s_names.Add(Commands.CloseAllButCurrent, "現在のタブ以外を閉じる");
			s_names.Add(Commands.CloseWindow, "ウィンドウを閉じる");
			s_names.Add(Commands.UndoClose, "閉じたタブを元に戻す");
			s_names.Add(Commands.ToggleTopMost, "最前面表示のON/OFF");
			s_names.Add(Commands.OpenTabBarOptionDialog, "オプションを開く");
			s_names.Add(Commands.OpenButtonBarOptionDialog, "ツールバーのカスタマイズ");

			//NOTE: 過去の保存値とハッシュが変わってしまうので使わない
			//s_translators.Add(Commands.GoBack, TargetIsOne);
			//s_translators.Add(Commands.GoForward, TargetIsOne);

			// 0.0.2
			s_names.Add(Commands.BrowseFolder, "フォルダを指定して開く");
			s_names.Add(Commands.MD5, "MD5ハッシュを計算");
			s_names.Add(Commands.ReorderTabsByName, "名前で並び替え");
			s_names.Add(Commands.ReorderTabsByPath, "パスで並び替え");
			s_names.Add(Commands.ReorderTabsByActv, "アクティブ順で並び替え");
			s_names.Add(Commands.ReorderTabsRevers, "逆順で並び替え");

			s_translators.Add(Commands.MD5, TargetToStringArray);
		}

		//private static object TargetIsOne(object target)
		//{
		//    return 1;
		//}

		private static object TargetToStringArray(BuiltInCommand cmd, Action.Arguments args)
		{
			//Address[] addrs = cmd.GetAddresses(args.Target);
			//if (addrs == null)
			//{
			//    return null;
			//}
			//string[] paths = Array.ConvertAll(addrs, delegate(Address addr)
			//{
			//    StringBuilder buf = new StringBuilder(260);
			//    if (SHGetPathFromIDListW(addr.ITEMIDLIST, buf))
			//    {
			//    }
			//});
			//paths = Array.FindAll(paths, str => str != null);
			//return paths;
			return cmd.GetFileNames(args.Target);
		}

		private static object TargetToAddressArray(BuiltInCommand cmd, Action.Arguments args)
		{
			return cmd.GetAddresses(args.Target);
		}

		[NonSerialized]
		private string m_name = null;

		private Commands m_command;
		private object m_arg;	// 固定パラメータ

		public override string Name
		{
			get
			{
				if (m_name == null)
				{
					//NOTE: シリアライズ対策のため、ここで初期化
					m_name = s_names[m_command];
				}
				return m_name;
			}
		}

		public BuiltInCommand(Commands command, object arg)
		{
			m_command = command;
			m_arg = arg;
		}

		public override void Execute(Arguments args)
		{
			object arg = m_arg;
			if (arg == null)
			{
				TargetTranslator translator = null;
				if (s_translators.TryGetValue(m_command, out translator))
				{
					arg = translator.Invoke(this, args);
				}
			}
			args.Server.ExecuteCommand(m_command, arg);
		}

		public override int GetHashCode()
		{
			//NOTE: enum のハッシュは0からのインデックスなので、m_argとは重みを変える
			return GetType().GetHashCode() ^ (m_command.GetHashCode() << 16) ^ (m_arg != null ? m_arg.GetHashCode() : 0);
		}
	}

	[Serializable]
	public class GestureExpression
	{
		public static readonly int[] EmptyTrails = new int[0];

		public virtual string Name
		{
			get
			{
				if (Action != null)
				{
					return Action.Name;
				}
				else
				{
					return null;
				}
			}
		}

		public int[] Trails { get; set; }
		public Action Action { get; set; }
		//public bool Edited { get; set; }

		public GestureExpression(int[] trails, Action action)
		{
			Trails = trails;
			Action = action;
			//Name = action.Name;	// デフォルト名を指定
		}

		public GestureExpression(string urdl, Action action)
			: this(Parse(urdl), action)
		{
		}

		private static int[] Parse(string urdl)
		{
			//if (urdl.Length == 0)
			//{
			//    throw new FormatException("string is empty");
			//}
			int[] trails = Array.ConvertAll(urdl.ToCharArray(), new Converter<char, int>(
				delegate(char c)
				{
					switch (Char.ToUpper(c))
					{
						case 'U':
						case '↑':
							return 0;
						case 'R':
						case '→':
							return 1;
						case 'D':
						case '↓':
							return 2;
						case '←':
						case 'L':
							return 3;
					}
					throw new FormatException(c + " of " + urdl);
				}));

			return trails;
		}

		public string FormatTrails(string urdl)
		{
			if (Trails == null)
			{
				return String.Empty;
			}
			char[] ar = Array.ConvertAll(Trails, new Converter<int, char>(
				delegate(int direction)
				{
					return urdl[direction];
				}));

			return new String(ar);
		}

		public string FormatTrails()
		{
			return FormatTrails("↑→↓←");
		}

		public bool TrailsEquals(int[] trails)
		{
			if (Trails.Length != trails.Length)
			{
				return false;
			}
			for (int i = 0; i < Trails.Length; i++)
			{
				if (Trails[i] != trails[i])
				{
					return false;
				}
			}
			return true;
		}

		public bool TrailsEquals(GestureExpression expr)
		{
			return TrailsEquals(expr.Trails);
		}

		public override string ToString()
		{
			return String.Format("{0} [{1}]", Name, FormatTrails());
		}
	}
}
